package 递归回溯;

import com.alibaba.fastjson.JSON;

import java.util.*;

/**
 * @description:
 * @author: 小白白
 * @create: 2021-09-30
 **/

public class No210课程表II {

    /**
     * 现在你总共有 numCourses 门课需要选，记为 0 到 numCourses - 1。
     * 给你一个数组 prerequisites ，其中 prerequisites[i] = [ai, bi] ，
     * 表示在选修课程 ai 前 必须 先选修 bi 。
     * 例如，想要学习课程 0 ，你需要先完成课程 1 ，我们用一个匹配来表示：[0,1] 。
     * 返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序，你只要返回 任意一种 就可以了。
     * 如果不可能完成所有课程，返回 一个空数组 。
     *
     * 示例 1：
     * 输入：numCourses = 2, prerequisites = [[1,0]]
     * 输出：[0,1]
     * 解释：总共有 2 门课程。要学习课程 1，你需要先完成课程 0。因此，正确的课程顺序为 [0,1] 。
     * 示例 2：
     * 输入：numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
     * 输出：[0,2,1,3]
     * 解释：总共有 4 门课程。要学习课程 3，你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
     * 因此，一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
     * 示例 3：
     * 输入：numCourses = 1, prerequisites = []
     * 输出：[0]
     *
     * 提示：
     * 1 <= numCourses <= 2000
     * 0 <= prerequisites.length <= numCourses * (numCourses - 1)
     * prerequisites[i].length == 2
     * 0 <= ai, bi < numCourses
     * ai != bi
     * 所有[ai, bi] 匹配 互不相同
     *  
     * 拓展：
     * 这个问题相当于查找一个循环是否存在于有向图中。如果存在循环，则不存在拓扑排序，因此不可能选取所有课程进行学习。
     * 通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程（21分钟），介绍拓扑排序的基本概念。
     * 拓扑排序也可以通过 BFS 完成。
     */

    private Map<Integer, Set<Integer>> map = new HashMap<>();
    private Deque<Integer> result = new ArrayDeque<>();

    public int[] findOrder(int numCourses, int[][] prerequisites) {
        for (int[] prerequisite : prerequisites) {
            Set<Integer> set = map.getOrDefault(prerequisite[0], new HashSet<>());
            set.add(prerequisite[1]);
            map.put(prerequisite[0], set);
        }
        for (int[] prerequisite : prerequisites) {
            if(dfs(new HashSet<>(), prerequisite[0])) {
                result.addLast(prerequisite[0]);
            }
        }
        int[] resultArr = new int[result.size()];
        for (int i = 0; i < result.size(); i++) {
            resultArr[i] = result.removeFirst();
        }
        return resultArr;
    }

    private boolean dfs(Set<Integer> path, Integer course) {
        if (path.contains(course)) {
            // 环了,退回
            return false;
        }
        Set<Integer> set = map.get(course);
        if (set == null || set.size() == 0) {
            // 可以学
            result.addLast(course);
            return true;
        }
        Set<Integer> canSet = new HashSet<>();
        // 不可学,有前置条件,需要进行过滤
        for (Integer i : set) {
            path.add(course);
            if(dfs(path, i)) {
                // i 学完了,那么它的一个前置条件就不存在了
                canSet.add(i);
            }
            path.remove(course);
        }
        set.removeAll(canSet);
        return set.size() == 0;
    }

    public static void main(String[] args) {
        No210课程表II n = new No210课程表II();
        int[][] arr = new int[][]{{1,0},{2,0},{3,1},{3,2}};
        int[] result = n.findOrder(4, arr);
        System.out.println(JSON.toJSONString(result));
    }

}
